#ifndef _UNIFORMBUFFER_H_
#define _UNIFORMBUFFER_H_


#include <unordered_map>
#include <string>
#include <memory>
#include <SERender/Drive/Drive.h>
#include <glm/gtc/type_ptr.hpp>

namespace SEngine
{

    class UniformBufferLoader;
    class Shader;

    class UniformBuffer
    {
    public:
        friend class UniformBufferLoader;
        
        struct Block
        {
            size_t offset;
            size_t size;
        };

    public:
        UniformBuffer(std::unique_ptr<IDrive> && drive, size_t totalSize, const std::string & name, uint32_t bindPoint = 1);
        virtual ~UniformBuffer();

        void Bind();
        void Unbind();

        uint32_t GetId() { return m_id; }
        uint32_t GetBindPoint() { return m_bindPoint; }

        template<typename T>
        void SetSubData(const std::string & name, T t);

        static void BindToShader(Shader & shader, const std::string & name, uint32_t bindPoint = 0);

    private:
        template<typename T>
        void SetSubData_(Block block, T t) { m_drive->BufferSubData(UNIFORM_BUFFER, block.offset, block.size, &t); }
        template<>
        void SetSubData_<glm::mat4>(Block block, glm::mat4 t) { m_drive->BufferSubData(UNIFORM_BUFFER, block.offset, block.size, glm::value_ptr(t)); }
        template<>
        void SetSubData_<glm::vec3>(Block block, glm::vec3 t) { m_drive->BufferSubData(UNIFORM_BUFFER, block.offset, block.size, &t.x); }
        template<>
        void SetSubData_<glm::vec4>(Block block, glm::vec4 t) { m_drive->BufferSubData(UNIFORM_BUFFER, block.offset, block.size, &t.x); }

    private:
        uint32_t m_id;
        uint32_t m_bindPoint;
        std::string m_name;
        std::unordered_map<std::string, Block> m_blocks;
        std::unique_ptr<IDrive> m_drive;
    };
    
    template <typename T>
    inline void UniformBuffer::SetSubData(const std::string &name, T t)
    {
        Bind();
        if (auto iter = m_blocks.find(name); iter != m_blocks.end()) {
            SetSubData_(iter->second, t);
        }
        Unbind();
    }
} // namespace SEngine

#endif